﻿/*
 *  Copyright 2009 Alessandro Pilotti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2.1 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 */

using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Data;

namespace AlexPilotti.L2SQLDataModel
{
    public static class DynamicAssemblyUtility
    {
        public static CustomAttributeBuilder EmitAttribute(Type type, IDictionary<string, object> properties)
        {
            string[] propertyNames = new string[properties.Count];
            properties.Keys.CopyTo(propertyNames, 0);

            object[] propertyValues = new object[properties.Count];
            properties.Values.CopyTo(propertyValues, 0);

            return EmitAttribute(type, propertyNames, propertyValues);
        }

        public static CustomAttributeBuilder EmitAttribute(Type type, string[] propertyNames, object[] propertyValues)
        {
            return EmitAttribute(type, Type.EmptyTypes, new object[0], propertyNames, propertyValues);
        }

        public static CustomAttributeBuilder EmitAttribute(Type type, Type[] constrParamTypes, object[] constrParamValues, string[] propertyNames, object[] propertyValues)
        {
            int i = 0;
            PropertyInfo[] pi = new PropertyInfo[propertyNames.Length];
            foreach (string propertyName in propertyNames)
                pi[i++] = type.GetProperty(propertyName);

            ConstructorInfo constructor = type.GetConstructor(constrParamTypes);
            return new CustomAttributeBuilder(constructor, constrParamValues, pi, propertyValues);
        }

        public static CustomAttributeBuilder EmitAttribute(Type type, Type[] constrParamTypes, object[] constrParamValues)
        {
            return EmitAttribute(type, constrParamTypes, constrParamValues, new string[0], new object[0]);
        }

        public static void EmitSimpleProperty(TypeBuilder tb, string name, Type type, string fieldPrefix, out PropertyBuilder pb, out FieldBuilder fb)
        {
            // Create a private field accessed by the property
            fb = tb.DefineField(fieldPrefix + name, type, FieldAttributes.Private);

            pb = tb.DefineProperty(name, System.Reflection.PropertyAttributes.HasDefault, type, null);

            // The property "set" and property "get" methods require a special set of attributes.
            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            // Define the "get" accessor method
            MethodBuilder getAccessor = tb.DefineMethod("get_" + name, getSetAttr, type, Type.EmptyTypes);

            ILGenerator getIL = getAccessor.GetILGenerator();
            // For an instance property, argument zero is the instance. Load the 
            // instance, then load the private field and return, leaving the
            // field value on the stack.
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fb);
            getIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for Number, which has no return
            // type and takes one argument of type int (Int32).
            MethodBuilder setAccessor = tb.DefineMethod("set_" + name, getSetAttr, null, new Type[] { type });

            ILGenerator setIL = setAccessor.GetILGenerator();
            // Load the instance and then the numeric argument, then store the
            // argument in the field.
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fb);
            setIL.Emit(OpCodes.Ret);

            // Last, map the "get" and "set" accessor methods to the 
            // PropertyBuilder. The property is now complete. 
            pb.SetGetMethod(getAccessor);
            pb.SetSetMethod(setAccessor);
        }

        public static string CapitalizeString(string str)
        {
            return Regex.Replace(str, @"[\w-[_]]+", delegate(Match m)
            {
                return char.ToUpper(m.Value[0]) + m.Value.Substring(1).ToLower();
            });
        }

        /// <summary>
        /// Returns an identifier name valid for C# and other languages
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string GetValidIdentifier(string name)
        {
            //return Regex.Replace(name, @"^[^_A-Za-z]|[^_0-9A-Za-z]|\s", "_");
            return Regex.Replace(name, @"^[\W\d]|[^\w]|\s", "_");
        }

        public static string EscapeTSQLIdentifier(string name)
        {
            if (name.Contains("[") || name.Contains("]"))
                throw new L2SQLDataModelException(string.Format("{0} is not a valid TSQL name", name));
            
            return "[" + name + "]";            
        }

        public static string CombineEscapedTSQLIdentifier(string schema, string name)
        {
            return ((schema == null || schema == "") ? "" : (EscapeTSQLIdentifier(schema) + ".")) + EscapeTSQLIdentifier(name);
        }

        public static SqlDbType GetSqlDbTypeFromDataColumn(DataColumn dc)
        {
            SqlDbType sqlDbType;

            Type t = dc.DataType;

            if (t.Equals(typeof(string)))
            {
                if (dc.MaxLength == -1 || dc.MaxLength > 4000)
                    sqlDbType = SqlDbType.NText;
                else
                    sqlDbType = SqlDbType.NVarChar;
            }
            else if (t.Equals(typeof(Int64)))
                sqlDbType = SqlDbType.BigInt;
            else if (t.Equals(typeof(Int32)))
                sqlDbType = SqlDbType.Int;
            else if (t.Equals(typeof(Int16)))
                sqlDbType = SqlDbType.SmallInt;
            else if (t.Equals(typeof(Byte)))
                sqlDbType = SqlDbType.TinyInt;
            else if (t.Equals(typeof(Boolean)))
                sqlDbType = SqlDbType.Bit;
            else if (t.Equals(typeof(Single)))
                sqlDbType = SqlDbType.Real;
            else if (t.Equals(typeof(Double)))
                sqlDbType = SqlDbType.Float;
            else if (t.Equals(typeof(DateTime)))
                sqlDbType = SqlDbType.DateTime;
            else if (t.Equals(typeof(Byte[])))
            {
                if (dc.MaxLength == -1 || dc.MaxLength > 8000)
                    sqlDbType = SqlDbType.Image;
                else
                    sqlDbType = SqlDbType.VarBinary;
            }
            else if (t.Equals(typeof(Decimal)))
                sqlDbType = SqlDbType.Decimal;
            else if (t.Equals(typeof(Guid)))
                sqlDbType = SqlDbType.UniqueIdentifier;
            else if (t.Equals(typeof(Object)))
                sqlDbType = SqlDbType.Variant;
            else
                throw new L2SQLDataModelException(string.Format("Unsupported Type to SqlDbType conversion: {0}", t.FullName));            

            return sqlDbType;
        }
    }
}
